
#include "airc.h"
#include "airc-cmds.h"

static void airc_send_cb(gpointer data, gint source, PurpleInputCondition cond);

const char *airc_list_icon(PurpleAccount *account, PurpleBuddy *buddy);
char *airc_status_text(PurpleBuddy *buddy);
void airc_toolkit_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info,
    gboolean full);
GList *airc_status_types(PurpleAccount *account);
GList *airc_chat_info(PurpleConnection *gc);
GHashTable *airc_chat_info_defaults(PurpleConnection *gc,
    const char *chat_name);
void airc_login(PurpleAccount *account);
gboolean airc_open(PurpleAccount *account, PurpleConnection *gc);
void airc_close(PurpleConnection *gc);
int airc_send_im(PurpleConnection *gc, const char *who, const char *message,
    PurpleMessageFlags flags);
void airc_get_info(PurpleConnection *gc, const char *who);
void airc_set_status(PurpleAccount *account, PurpleStatus *status);
void airc_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy,
    PurpleGroup *group);
void airc_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy,
    PurpleGroup *group);
void airc_add_deny(PurpleConnection *gc, const char *name);
void airc_rem_deny(PurpleConnection *gc, const char *name);
void airc_join_chat(PurpleConnection *gc, GHashTable *components);
char *airc_get_chat_name(GHashTable *components);
void airc_chat_invite(PurpleConnection *gc, int id, const char *message,
    const char *who);
void airc_chat_leave(PurpleConnection *gc, int id);
void airc_chat_whisper(PurpleConnection *gc, int id, const char *who,
    const char *message);
int airc_chat_send(PurpleConnection *gc, int id, const char *message,
    PurpleMessageFlags flags);
void airc_keepalive(PurpleConnection *gc);
void airc_register_user(PurpleAccount *account);
void airc_set_chat_topic(PurpleConnection *gc, int id, const char *topic);
PurpleRoomlist *airc_roomlist_get_list(PurpleConnection *gc);
void airc_roomlist_cancel(PurpleRoomlist *list);
gboolean airc_can_receive_file(PurpleConnection *gc, const char *who);
void airc_send_file(PurpleConnection *gc, const char *who,
    const char *filename);
PurpleXfer *airc_new_xfer(PurpleConnection *gc, const char *who);
gboolean airc_offline_message(const PurpleBuddy *buddy);
int airc_send_raw(PurpleConnection *gc, const char *buf, int len);
gboolean airc_send_attention(PurpleConnection *gc, const char *username,
    guint type);
GList *airc_get_attention_types(PurpleAccount *acct);
void airc_set_public_alias(PurpleConnection *gc, const char *alias,
    PurpleSetPublicAliasSuccessCallback success_cb,
    PurpleSetPublicAliasFailureCallback failure_cb);
void airc_get_public_alias(PurpleConnection *gc,
    PurpleGetPublicAliasSuccessCallback success_cb,
    PurpleGetPublicAliasFailureCallback failure_cb);

gboolean load(PurplePlugin *plugin);
gboolean unload(PurplePlugin *plugin);
void destroy(PurplePlugin *plugin);

PurplePlugin *airc_plugin = NULL;

int airc_send(IRC_conn *irc, const char *buf, int len){
    char *trunc;
    int ret;
    
    trunc = g_strndup(buf, len);
    ret = irc_send(irc, trunc);
    g_free(trunc);
    return ret;
}

static int airc_write(IRC_conn *irc, const char *buf, gsize len){
    return (irc->gsc) ? purple_ssl_write(irc->gsc, buf, len) : 
        write(irc->fd, buf, len);
}

int irc_send(IRC_conn *irc, const char *format, ...){
    int ret, buflen, flen;
    char *toSend, *tmp, *format_n;
    va_list args;
    
    va_start(args, format);
    flen = strlen(format);
    format_n = g_strndup(format, flen+2);
    format_n[flen] = '\r';
    format_n[flen+1] = '\n';
    buflen = g_vasprintf(&toSend, format_n, args);
    g_free(format_n);
    va_end(args);
    if(toSend == NULL)
        return 0;
    purple_signal_emit(airc_plugin, "irc-sending-text",
        purple_account_get_connection(irc->account), &toSend);
    purple_debug_info("airc", "sending: %s", toSend);
    if (!irc->writeh)
        ret = airc_write(irc, toSend, buflen);
    else{
        ret = -1;
        errno = EAGAIN;
    }
    if((ret <= 0) && (errno != EAGAIN)){
        PurpleConnection *gc = purple_account_get_connection(irc->account);
        tmp = g_strdup_printf("Lost connection with server: %s",
            g_strerror(errno));
        purple_connection_error_reason (gc,
            PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
        g_free(tmp);
    }else if(ret < buflen){
        if(ret < 0)
            ret = 0;
        if(!irc->writeh)
            irc->writeh = purple_input_add((irc->gsc) ? irc->gsc->fd : irc->fd,
                PURPLE_INPUT_WRITE, airc_send_cb, irc);
        purple_circ_buffer_append(irc->outbuf, toSend + ret, buflen - ret);
    }
    g_free(toSend);
    return ret;
}

static void airc_send_cb(gpointer data, gint source, PurpleInputCondition cond){
    IRC_conn *irc;
    int ret, writelen;
    PurpleConnection *gc;
    gchar *tmp;

    irc = (IRC_conn*)data;
    writelen = purple_circ_buffer_get_max_read(irc->outbuf);
    if(writelen == 0){
        purple_input_remove(irc->writeh);
        irc->writeh = 0;
        return;
    }
    ret = airc_write(irc, irc->outbuf->outptr, writelen);
    if(ret < 0 && errno == EAGAIN)
        return;
    else if(ret <= 0){
        gc = purple_account_get_connection(irc->account);
        tmp = g_strdup_printf("Lost connection with server: %s",
            g_strerror(errno));
        purple_connection_error_reason (gc,
            PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
        g_free(tmp);
        return;
    }
    purple_circ_buffer_mark_read(irc->outbuf, ret);
}

const char *airc_list_icon(PurpleAccount *account, PurpleBuddy *buddy){
    return "irc";
}

char *airc_status_text(PurpleBuddy *buddy){
    PurplePresence *presence;
    PurpleStatus *status;
    char *ret;
    
    if(!buddy)
        return NULL;
    presence = purple_buddy_get_presence(buddy);
    status = purple_presence_get_status(presence, "away");
    ret = (char *)purple_status_get_attr_string(status, "message");
    return (ret) ? g_strdup(ret) : NULL;
}

void airc_toolkit_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info,
    gboolean full){
    return;
}

GList *airc_status_types(PurpleAccount *account){
        PurpleStatusType *type;
        GList *types = NULL;

        type = purple_status_type_new(PURPLE_STATUS_AVAILABLE, NULL, NULL, TRUE);
        types = g_list_append(types, type);
        type = purple_status_type_new_with_attrs(
                PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
                "message", "Message", purple_value_new(PURPLE_TYPE_STRING),
                NULL);
        types = g_list_append(types, type);
        /* Offline status type remains offline when pidgin reconnects */
        type = purple_status_type_new(PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE);
        types = g_list_append(types, type);
        /* Quit status type comes back online as availble when pidgin reconnects */
        type = purple_status_type_new(PURPLE_STATUS_OFFLINE, "quit", "Quit", TRUE);
        types = g_list_append(types, type);
        return types;
}

GList *airc_chat_info(PurpleConnection *gc){
        GList *m = NULL;
        struct proto_chat_entry *pce;

        pce = g_new0(struct proto_chat_entry, 1);
        pce->label = "_Channel:";
        pce->identifier = "channel";
        pce->required = TRUE;
        m = g_list_append(m, pce);
        pce = g_new0(struct proto_chat_entry, 1);
        pce->label = "_Key:";
        pce->identifier = "key";
        pce->secret = TRUE;
        m = g_list_append(m, pce);
        
        /* TODO: autojoin, detachable */

        return m;
}

GHashTable *airc_chat_info_defaults(PurpleConnection *gc,
    const char *chat_name){
        GHashTable *defaults;

        defaults = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
        if (chat_name != NULL)
                g_hash_table_insert(defaults, "channel", g_strdup(chat_name));
        return defaults;
}

static guint airc_nick_hash(const char *nick){
    char *lc;
    guint bucket;
    
    lc = g_utf8_strdown(nick, -1);
    bucket = g_str_hash(lc);
    g_free(lc);
    return bucket;
}

static gboolean airc_nick_equal(const char *nick1, const char *nick2){
    return (purple_utf8_strcasecmp(nick1, nick2) == 0);
}

static void airc_buddy_free(IRC_buddy *ib){
    g_free(ib->name);
    g_free(ib);
}

void airc_login(PurpleAccount *account){
    char *accname, **userparts;
    IRC_conn *irc;
    PurpleConnection *gc;
    
    accname = (char*)purple_account_get_username(account);
    userparts = g_strsplit(accname, "@", 2);
    gc = purple_account_get_connection(account);
    purple_connection_set_display_name(gc, userparts[0]);
    if(strpbrk(userparts[0], " \t\v\r\n") != NULL){
        purple_connection_error_reason(gc,
            PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
            "IRC nick may not contain whitespace");
        return;
    }
    g_strfreev(userparts);
    gc->flags = PURPLE_CONNECTION_NO_NEWLINES | PURPLE_CONNECTION_HTML;
    gc->proto_data = irc = g_new0(IRC_conn, 1);
    irc->account = account;
    irc->outbuf = purple_circ_buffer_new(512);
    irc->buddies = g_hash_table_new_full((GHashFunc)airc_nick_hash,
        (GEqualFunc)airc_nick_equal, NULL, (GDestroyNotify)airc_buddy_free);
    if(airc_open(account, gc) == FALSE)
        purple_connection_error_reason(gc,
            PURPLE_CONNECTION_ERROR_NETWORK_ERROR, "Unable to connect");
    return;
}

static gboolean do_login(PurpleConnection *gc){
    const char *pass, *nick, *gecos, *ident;
    char *tmp, c, default_gecos[] = IRC_DEFAULT_GECOS;
    IRC_conn *irc;

    irc =  gc->proto_data;
    pass = purple_connection_get_password(gc);
    if(pass && *pass){
        if(irc_send(irc, "PASS %s", pass) < 0)
            return FALSE;
    }
    nick = purple_connection_get_display_name(gc);
    if(irc_send(irc, "NICK %s", nick) < 0)
        return FALSE;
    gecos = purple_account_get_string(irc->account, "realname", "");
    ident = purple_account_get_string(irc->account, "ident", "");
    if(ident == NULL || *ident == '\0')
            ident = g_get_user_name();
    if(gecos == NULL || *gecos == '\0')
            gecos = default_gecos;
    tmp = (char*)ident;
    while((c = tmp[0])){
        if(g_ascii_isspace(c))
            tmp[0] = '_';
        tmp++;
    }
    if(irc_send(irc, "USER %s * * :%s", ident, gecos) < 0)
        return FALSE;
    irc->lastping = time(NULL);
    return TRUE;
}

static void read_input(IRC_conn *irc, int len){
    char *cur, *end;
    int step;

    irc->account->gc->last_received = time(NULL);
    irc->inbufused += len;
    irc->inbuf[irc->inbufused] = '\0';
    cur = irc->inbuf;
    /* TODO: I have no idea what this hack is... */
    /* This is a hack to work around the fact that marv gets messages
     * with null bytes in them while using some weird irc server at work
     */
    while((cur < (irc->inbuf + irc->inbufused)) && !*cur)
        cur++;
    while(cur < irc->inbuf + irc->inbufused &&
           ((end = strstr(cur, "\r\n")) || (end = strstr(cur, "\n")))) {
        step = (*end == '\r' ? 2 : 1);
        *end = '\0';
        airc_parse_msg(irc, cur);
        cur = end + step;
    }
    if(cur != irc->inbuf + irc->inbufused){ /* leftover */
        irc->inbufused -= (cur - irc->inbuf);
        memmove(irc->inbuf, cur, irc->inbufused);
    }else{
        irc->inbufused = 0;
    }
}

static void airc_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
    PurpleInputCondition cond){
    PurpleConnection *gc;
    IRC_conn *irc;
    int len;
    gchar *tmp;

    gc = (PurpleConnection*)data;
    irc = IRCDATA(gc);
    if(!g_list_find(purple_connections_get_all(), gc)){
        purple_ssl_close(gsc);
        return;
    }
    if(irc->inbuflen < irc->inbufused + IRC_INITIAL_BUFSIZE){
        irc->inbuflen += IRC_INITIAL_BUFSIZE;
        irc->inbuf = g_realloc(irc->inbuf, irc->inbuflen);
    }
    len = purple_ssl_read(gsc, irc->inbuf + irc->inbufused,
        IRC_INITIAL_BUFSIZE - 1);
    if((len < 0) && (errno == EAGAIN)) {
        return;
    }else if(len < 0){
        tmp = g_strdup_printf("Lost connection with server: %s",
            g_strerror(errno));
        purple_connection_error_reason(gc,
            PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
        g_free(tmp);
        return;
    }else if(len == 0){
        purple_connection_error_reason(gc,
            PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
            "Server closed the connection");
        return;
    }
    read_input(irc, len);
}

static void airc_input_cb(gpointer data, gint source,
    PurpleInputCondition cond){
    PurpleConnection *gc;
    IRC_conn *irc;
    int len;
    gchar *tmp;

    gc = (PurpleConnection*)data;
    irc = IRCDATA(gc);
    if(irc->inbuflen < irc->inbufused + IRC_INITIAL_BUFSIZE){
        irc->inbuflen += IRC_INITIAL_BUFSIZE;
        irc->inbuf = g_realloc(irc->inbuf, irc->inbuflen);
    }
    len = read(irc->fd, irc->inbuf + irc->inbufused, IRC_INITIAL_BUFSIZE - 1);
    if((len < 0) && (errno == EAGAIN)){
        return;
    }else if(len < 0){
        tmp = g_strdup_printf("Lost connection with server: %s",
            g_strerror(errno));
        purple_connection_error_reason (gc,
            PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
        g_free(tmp);
        return;
    } else if (len == 0) {
        purple_connection_error_reason (gc,
            PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
            "Server closed the connection");
        return;
    }
    read_input(irc, len);
}

static void airc_login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
    PurpleInputCondition cond){

    if(do_login((PurpleConnection *)data))
        purple_ssl_input_add(gsc, airc_input_cb_ssl, data);
}

static void airc_ssl_connect_failure(PurpleSslConnection *gsc,
    PurpleSslErrorType error, gpointer data){
    PurpleConnection *gc;
    IRC_conn *irc;
    
    gc = (PurpleConnection*)data;
    irc = IRCDATA(gc);
    irc->gsc = NULL;
    purple_connection_ssl_error(gc, error);
}

static void airc_login_cb(gpointer data, gint source,
    const gchar *error_message){
    PurpleConnection *gc;
    IRC_conn *irc;
    
    gc = (PurpleConnection*)data;
    irc = gc->proto_data;
    if(source < 0){
        gchar *tmp = g_strdup_printf("Unable to connect: %s", error_message);
        purple_connection_error_reason(gc,
            PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
        g_free(tmp);
        return;
    }
    irc->fd = source;
    if(do_login(gc)){
        gc->inpa = purple_input_add(irc->fd, PURPLE_INPUT_READ, airc_input_cb, gc);
    }
}


gboolean airc_open(PurpleAccount *account, PurpleConnection *gc){
    char *server, **servers, **hostparts;
    IRC_conn *irc;
    int s = 0, port;
    
    irc = gc->proto_data;
    purple_connection_update_progress(gc, "Connecting", 1, 2);
    servers = g_strsplit(purple_account_get_string(account, "servers", "none"),
        ",", -1);
    while((server = servers[s++])){
        purple_debug_info("airc", "Trying connection to %s\n", server);
        if(!g_ascii_strcasecmp(server, "none"))
            break;
        hostparts = g_strsplit(server, ":", 2);
        if(!hostparts[1]){
            if(purple_proxy_connect(gc, account, hostparts[0],
                IRC_DEFAULT_PORT, airc_login_cb, gc)){
                    g_strfreev(hostparts);
                    return TRUE;
            }
        }else{
            if(hostparts[1][0] == '+'){
                if(!purple_ssl_is_supported()){
                    g_strfreev(hostparts);
                    continue;
                }
                port = (int)g_ascii_strtoll(hostparts[1]+1, NULL, 10);
                irc->gsc = purple_ssl_connect(account, hostparts[0], port,
                    airc_login_cb_ssl, airc_ssl_connect_failure, gc);
                if(irc->gsc){
                    g_strfreev(hostparts);
                    return TRUE;
                }
            }else{
                port = (int)g_ascii_strtoll(hostparts[1], NULL, 10);
                if(purple_proxy_connect(gc, account, hostparts[0],
                    port, airc_login_cb, gc)){
                        g_strfreev(hostparts);
                        return TRUE;
                }
            }
        }
        g_strfreev(hostparts);
    }
    g_strfreev(servers);
    return FALSE;
}

void airc_close(PurpleConnection *gc){
    IRC_conn *irc;
    
    irc = gc->proto_data;
    if (irc == NULL)
        return;
    if (irc->gsc || (irc->fd >= 0))
        airc_usercmd_quit(irc, "quit", NULL, NULL);
    if (gc->inpa)
        purple_input_remove(gc->inpa);
    g_free(irc->inbuf);
    if(irc->gsc)
        purple_ssl_close(irc->gsc);
    else if(irc->fd >= 0)
        close(irc->fd);
    if(irc->timer)
        purple_timeout_remove(irc->timer);
    g_hash_table_destroy(irc->buddies);
    if(irc->writeh)
        purple_input_remove(irc->writeh);
    purple_circ_buffer_destroy(irc->outbuf);
    g_free(irc);
    gc->proto_data = NULL;
}

int airc_send_im(PurpleConnection *gc, const char *who, const char *message,
    PurpleMessageFlags flags){
    IRC_conn *irc;
    char *plain;
    char *args;

    irc = gc->proto_data;
    if(*message == '/'){
        airc_parse_rawcmd(irc, who, message + 1);
        return 0;
    }
    purple_markup_html_to_xhtml(message, NULL, &plain);
    /* TODO: Turn XHTML into IRC */
    args = g_strdup_printf("%s %s", airc_nick_skip_mode(irc, (char *)who), plain);
    airc_usercmd_privmsg(irc, "msg", NULL, (const char **)&args);
    g_free(plain);
    g_free(args);
    return 1;
}

void airc_get_info(PurpleConnection *gc, const char *who){
    const char *args[2];
    IRC_conn *irc;
    
    irc = gc->proto_data;
    args[0] = who;
    args[1] = who;
    airc_usercmd_whois(irc, "whois", NULL, args);
}

void airc_set_status(PurpleAccount *account, PurpleStatus *status){
    PurpleConnection *gc;
    IRC_conn *irc;
    const char *args;
    const char *status_id;
    
    status_id = purple_status_get_id(status);
    gc = purple_account_get_connection(account);
    if(!gc)
        return;
    irc = gc->proto_data;
    if(!purple_status_is_active(status))
        return;
    args = NULL;
    if(!strcmp(status_id, "away")){
        args = purple_status_get_attr_string(status, "message");
        if ((args == NULL) || (*args == '\0'))
            args = "Away";
        airc_usercmd_away(irc, "away", NULL, &args);
    }else if(!strcmp(status_id, "available")){
        airc_usercmd_away(irc, "away", NULL, &args);
    }
}

void airc_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy,
    PurpleGroup *group){
    IRC_conn *irc;
    IRC_buddy *ib;
    const char *bname;
    
    bname = purple_buddy_get_name(buddy);
    irc = IRCDATA(gc);
    ib = g_hash_table_lookup(irc->buddies, bname);
    if(ib != NULL){
        ib->ref++;
        purple_prpl_got_user_status(irc->account, bname,
            ib->online ? "available" : "offline", NULL);
    }else{
        ib = g_new0(IRC_buddy, 1);
        ib->name = g_strdup(bname);
        ib->ref = 1;
        g_hash_table_replace(irc->buddies, ib->name, ib);
        if(irc->features.watch)
            irc_send(irc, "WATCH +%s", ib->name);
    }
}

void airc_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy,
    PurpleGroup *group){
    IRC_conn *irc = IRCDATA(gc);
    IRC_buddy *ib;
    const char *bname;
    
    bname = purple_buddy_get_name(buddy);
    ib = g_hash_table_lookup(irc->buddies, bname);
    if(ib && --ib->ref == 0)
        g_hash_table_remove(irc->buddies, bname);
}

void airc_add_deny(PurpleConnection *gc, const char *name){
    
}

void airc_rem_deny(PurpleConnection *gc, const char *name){
    
}

void airc_join_chat(PurpleConnection *gc, GHashTable *components){
    IRC_conn *irc;
    const char *args[2];

    irc = IRCDATA(gc);
    args[0] = g_hash_table_lookup(components, "channel");
    args[1] = g_hash_table_lookup(components, "key");
    airc_usercmd_join(irc, "join", NULL, args);
}

char *airc_get_chat_name(GHashTable *components){
    return g_strdup(g_hash_table_lookup(components, "channel"));
}

void airc_chat_invite(PurpleConnection *gc, int id, const char *message,
    const char *who){
    /* TODO: I have no idea what this function is for. need to figure it out */
    IRC_conn *irc;
    PurpleConversation *convo;
    const char *args[2];

    convo = purple_find_chat(gc, id);
    irc = IRCDATA(gc);
    if (!convo) {
            purple_debug(PURPLE_DEBUG_ERROR, "irc", "Got chat invite request for bogus chat\n");
            return;
    }
    args[0] = who;
    args[1] = purple_conversation_get_name(convo);
    airc_usercmd_invite(irc, "invite", purple_conversation_get_name(convo), args);
}

void airc_chat_leave(PurpleConnection *gc, int id){
    IRC_conn *irc;
    PurpleConversation *convo;
    const char *args[1];

    convo = purple_find_chat(gc, id);
    irc = IRCDATA(gc);
    if (!convo)
            return;
    args[0] = g_strjoin(" ", purple_conversation_get_name(convo),
        purple_account_get_string(irc->account, "partmessage", NULL), NULL);
    airc_usercmd_part(irc, "part", purple_conversation_get_name(convo), args);
    serv_got_chat_left(gc, id);
}

void airc_chat_whisper(PurpleConnection *gc, int id, const char *who,
    const char *message){
    char *args, *tmp;
    IRC_conn *irc;
    
    irc = IRCDATA(gc);
    purple_markup_html_to_xhtml(message, NULL, &tmp);
    args = g_strdup_printf("%s %s", who, tmp);
    airc_usercmd_notice(irc, "notice", NULL, (const char **)&args);
    g_free(tmp);
    g_free(args);
}

int airc_chat_send(PurpleConnection *gc, int id, const char *message,
    PurpleMessageFlags flags){
    IRC_conn *irc;
    PurpleConversation *convo;
    char *args;
    char *tmp;

    convo = purple_find_chat(gc, id);
    irc = IRCDATA(gc);
    if(!convo){
            purple_debug(PURPLE_DEBUG_ERROR, "irc", "chat send on nonexistent chat\n");
            return -EINVAL;
    }
    if(*message == '/'){
        airc_parse_rawcmd(irc, convo->name, message + 1);
        return 0;
    }
    purple_markup_html_to_xhtml(message, NULL, &tmp);
    /* TODO: xhtml parsing again */
    args = g_strdup_printf("%s %s", convo->name, tmp);
    airc_usercmd_privmsg(irc, "msg", NULL, (const char **)&args);
    serv_got_chat_in(gc, id, purple_connection_get_display_name(gc), flags, message, time(NULL));
    g_free(tmp);
    g_free(args);
    return 0;
}

void airc_keepalive(PurpleConnection *gc){
    IRC_conn *irc;
    irc = IRCDATA(gc);
    if ((time(NULL) - irc->lastping) > PING_TIMEOUT)
        airc_usercmd_ping(irc, NULL, NULL, NULL);
}

void airc_register_user(PurpleAccount *account){
    /* NOTE: Users have to register manually if the IRC services require an
     email. This will also fail if users aren't allowed to register at login*/
    PurpleConnection *gc;
    IRC_conn *irc;
    const char *authnick, *nickpass;
    
    gc = purple_account_get_connection(account);
    if (gc == NULL)
        return;
    irc = IRCDATA(gc);
    if((authnick = purple_account_get_string(irc->account, "authnick", NULL)) &&
        (nickpass = purple_account_get_string(irc->account, "nickpass", NULL))){
            irc_send(irc, "PRIVMSG %s :REGISTER %s", authnick, nickpass);
    }
}

void airc_set_chat_topic(PurpleConnection *gc, int id, const char *topic){
    const char *name = NULL;
    IRC_conn *irc;

    irc = IRCDATA(gc);
    if(!(name = purple_conversation_get_name(purple_find_chat(gc, id))))
        return;
    irc_send(irc, "TOPIC %s :%s", name, topic);
}

PurpleRoomlist *airc_roomlist_get_list(PurpleConnection *gc){
    IRC_conn *irc;
    GList *fields = NULL;
    PurpleRoomlistField *f;

    irc = IRCDATA(gc);
    if(irc->roomlist)
        purple_roomlist_unref(irc->roomlist);
    irc->roomlist = purple_roomlist_new(purple_connection_get_account(gc));
    f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, "", "channel", TRUE);
    fields = g_list_append(fields, f);
    /* f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_BOOL, "Requires Key", "needkey", FALSE);
    fields = g_list_append(fields, f); */
    f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_INT, "Users", "users", FALSE);
    fields = g_list_append(fields, f);
    f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, "Topic", "topic", FALSE);
    fields = g_list_append(fields, f);
    purple_roomlist_set_fields(irc->roomlist, fields);
    if(irc->listCmd){
        irc_send(irc, irc->listCmd);
        g_free(irc->listCmd);
        irc->listCmd = NULL;
    }else
        irc_send(irc, "LIST");
    return irc->roomlist;
}

void airc_roomlist_cancel(PurpleRoomlist *list){
    PurpleConnection *gc;
    IRC_conn *irc;

    gc = purple_account_get_connection(list->account);
    if (gc == NULL)
        return;
    irc = IRCDATA(gc);
    purple_roomlist_set_in_progress(list, FALSE);
    if (irc->roomlist == list) {
        irc->roomlist = NULL;
        purple_roomlist_unref(list);
    }
}

gboolean airc_can_receive_file(PurpleConnection *gc, const char *who){
    return FALSE;
}

void airc_send_file(PurpleConnection *gc, const char *who,
    const char *filename){
    
}

PurpleXfer *airc_new_xfer(PurpleConnection *gc, const char *who){
    return NULL;
}

gboolean airc_offline_message(const PurpleBuddy *buddy){
    return FALSE;
}

int airc_send_raw(PurpleConnection *gc, const char *buf, int len){
    IRC_conn *irc;
    
    irc = IRCDATA(gc);
    if(len == -1)
        len = strlen(buf);
    airc_send(irc, buf, len);
    return len;
}

static GList *airc_actions(PurplePlugin *plugin, gpointer context){
    GList *list = NULL;
    /*PurplePluginAction *act = NULL;

    act = purple_plugin_action_new("View MOTD", irc_view_motd);
    list = g_list_append(list, act);*/

    return list;
}

static gboolean airc_load(PurplePlugin *plugin){
    purple_signal_register(plugin, "irc-sending-text",
        purple_marshal_VOID__POINTER_POINTER, NULL, 2,
        purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
        purple_value_new_outgoing(PURPLE_TYPE_STRING));
    purple_signal_register(plugin, "irc-receiving-text",
        purple_marshal_VOID__POINTER_POINTER, NULL, 2,
        purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
        purple_value_new_outgoing(PURPLE_TYPE_STRING));
    purple_debug_info("airc", "AIRC: Plugin loaded\n");
    return TRUE;
}

static void airc_destroy(PurplePlugin *plugin){
    purple_debug_info("airc", "plugin_destroy\n");
    purple_signal_unregister(plugin, "irc-sending-text");
    purple_signal_unregister(plugin, "irc-receiving-text");
    airc_unregister_usercmds();
}

static PurplePluginProtocolInfo extra_info = {
    OPT_PROTO_CHAT_TOPIC | OPT_PROTO_PASSWORD_OPTIONAL,
    NULL,
    NULL,
    NO_BUDDY_ICONS,
    airc_list_icon,
    NULL,
    airc_status_text,
    airc_toolkit_text,
    airc_status_types,
    NULL,
    airc_chat_info,
    airc_chat_info_defaults,
    airc_login,
    airc_close,
    airc_send_im,
    NULL,
    NULL,
    airc_get_info,
    airc_set_status,
    NULL,
    NULL,
    airc_add_buddy,
    NULL,
    airc_remove_buddy,
    NULL,
    NULL,
    airc_add_deny,
    NULL,
    airc_rem_deny,
    NULL,
    airc_join_chat,
    NULL,
    airc_get_chat_name,
    airc_chat_invite,
    airc_chat_leave,
    airc_chat_whisper,
    airc_chat_send,
    airc_keepalive,
    airc_register_user,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    purple_normalize_nocase,
    NULL,
    NULL,
    NULL,
    airc_set_chat_topic,
    NULL,
    airc_roomlist_get_list,
    airc_roomlist_cancel,
    NULL,
    airc_can_receive_file,
    airc_send_file,
    airc_new_xfer,
    airc_offline_message,
    NULL,
    airc_send_raw,
    NULL,
    NULL,
    NULL,
    NULL,
    sizeof(PurplePluginProtocolInfo),
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL
};

static PurplePluginInfo info = {
    PURPLE_PLUGIN_MAGIC,
    PURPLE_MAJOR_VERSION,
    PURPLE_MINOR_VERSION,
    PURPLE_PLUGIN_PROTOCOL,
    NULL,
    0,
    NULL,
    PURPLE_PRIORITY_DEFAULT,
    "advanced-irc",
    "[Advanced] IRC",
    "r Alpha01",
    "Advanced IRC Protocol Plugin",
    "An in-depth IRC plugin",
    "David Alphus <alpha@acuityservices.org>",
    "http://acuityservices.org/client",
    airc_load,
    NULL,
    airc_destroy,
    NULL,
    &extra_info,
    NULL,
    airc_actions,
    NULL,
    NULL,
    NULL,
    NULL
};

static void airc_init_plugin(PurplePlugin *plugin)
{
    PurpleAccountUserSplit *split;
    PurpleAccountOption *option;
    /*PurpleKeyValuePair *kvp;
    GList *list = NULL;*/

    split = purple_account_user_split_new("Network", IRC_DEFAULT_NETWORK, '@');
    extra_info.user_splits = g_list_append(extra_info.user_splits, split);

    option = purple_account_option_string_new("Servers", "servers",
        IRC_DEFAULT_SERVERS);
    extra_info.protocol_options = g_list_append(extra_info.protocol_options,
        option);

   /* kvp = g_new0(PurpleKeyValuePair, 1);
    kvp->key = g_strdup("Unreal IRCd");
    kvp->value = g_strdup("unreal");
    list = g_list_prepend(list, kvp);
    kvp = g_new0(PurpleKeyValuePair, 1);
    kvp->key = g_strdup("InspIRCd");
    kvp->value = g_strdup("inspircd");
    list = g_list_prepend(list, kvp);
    option = purple_account_option_list_new("Protocol", "protocol", list);
    extra_info.protocol_options = g_list_append(extra_info.protocol_options,
        option);*/

    option = purple_account_option_string_new("Encodings", "encoding",
        IRC_DEFAULT_CHARSET);
    extra_info.protocol_options = g_list_append(extra_info.protocol_options,
        option);

    option = purple_account_option_bool_new("Auto-detect incoming UTF-8",
        "autodetect_utf8", IRC_DEFAULT_AUTODETECT);
    extra_info.protocol_options = g_list_append(extra_info.protocol_options,
        option);

    option = purple_account_option_string_new("Ident", "ident", "");
    extra_info.protocol_options = g_list_append(extra_info.protocol_options,
        option);

    option = purple_account_option_string_new("Real name (GECOS)", "realname",
        IRC_DEFAULT_GECOS);
    extra_info.protocol_options = g_list_append(extra_info.protocol_options,
        option);

    option = purple_account_option_string_new("Quit Message", "quitmessage",
        IRC_DEFAULT_QUIT);
    extra_info.protocol_options = g_list_append(extra_info.protocol_options,
        option);
    
    option = purple_account_option_string_new("Part Message", "partmessage",
        IRC_DEFAULT_PART);
    extra_info.protocol_options = g_list_append(extra_info.protocol_options,
        option);
    
    option = purple_account_option_string_new("Auth Nick", "authnick",
        "NickServ");
    extra_info.protocol_options = g_list_append(extra_info.protocol_options,
        option);
    
    option = purple_account_option_string_new("Nick Password", "nickpass",
        "");
    purple_account_option_set_masked(option, TRUE);
    extra_info.protocol_options = g_list_append(extra_info.protocol_options,
        option);
    
    option = purple_account_option_string_new("Connect Command", "connectcmd",
        "");
    extra_info.protocol_options = g_list_append(extra_info.protocol_options,
        option); 

    airc_plugin = plugin;

    airc_register_usercmds();
}

PURPLE_INIT_PLUGIN(airc, airc_init_plugin, info);
